package cc.admin.common.util.dynamic.db;

import cc.admin.common.exception.CcAdminException;
import cc.admin.common.sys.vo.DynamicDataSourceModel;
import cc.admin.common.util.ReflectHelper;
import cc.admin.common.util.oConvertUtils;
import cc.admin.common.util.security.SecurityTools;
import com.alibaba.druid.pool.DruidDataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ArrayUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Spring JDBC 实时数据库访问
 *
 * @author chenguobin
 * @version 1.0
 * @date 2014-09-05
 */
@Slf4j
public class DynamicDBUtil {

	/**
	 * 获取数据源【最底层方法，不要随便调用】
	 *
	 * @param dbSource
	 * @param safeModel 安全模式，如果为空，那么默认就是安全模式，除非显式指定为false，否则禁止执行除本机数据源以外的更新操作。
	 * @return
	 */
	public static DruidDataSource getJdbcDataSource(final DynamicDataSourceModel dbSource, Boolean safeModel) {
		DruidDataSource dataSource = new DruidDataSource();
		String driverClassName = dbSource.getDbDriver();
		String url = dbSource.getDbUrl();
		if (safeModel == null || safeModel) {
			if (url.indexOf("localhost") == -1 && url.indexOf("127.0.0.1") == -1) {
				throw new RuntimeException("安全模式下禁止访问除localhost和127.0.0.1以外的数据源。当前URL为：" + url);
			}
		}
		String dbUser = dbSource.getDbUsername();
		String dbPassword = dbSource.getDbPassword();
		dataSource.setDriverClassName(driverClassName);
		dataSource.setUrl(url);
		//dataSource.setValidationQuery("SELECT 1 FROM DUAL");
		dataSource.setTestWhileIdle(true);
		dataSource.setTestOnBorrow(false);
		dataSource.setTestOnReturn(false);
		dataSource.setBreakAfterAcquireFailure(true);
		dataSource.setConnectionErrorRetryAttempts(0);
		dataSource.setUsername(dbUser);
		dataSource.setMaxWait(60000);
		dataSource.setPassword(SecurityTools.aseDecode(dbPassword));
		log.info("******************************************");
		log.info("*                                        *");
		log.info("*====【" + dbSource.getCode() + "】=====Druid连接池已启用 ====*");
		log.info("*                                        *");
		log.info("******************************************");
		return dataSource;
	}

	/**
	 * 通过 dbKey ,获取数据源
	 *
	 * @param dbKey
	 * @param safeModel 安全模式，如果为空，那么默认就是安全模式，除非显式指定为false，否则禁止执行除本机数据源以外的更新操作。
	 * @return
	 */
	public static DruidDataSource getDbSourceByDbKey(final String dbKey, Boolean safeModel) {
		//获取多数据源配置
		DynamicDataSourceModel dbSource = DataSourceCachePool.getCacheDynamicDataSourceModel(dbKey);
		//先判断缓存中是否存在数据库链接
		DruidDataSource cacheDbSource = DataSourceCachePool.getCacheBasicDataSource(dbKey);
		if (cacheDbSource != null && !cacheDbSource.isClosed()) {
			log.debug("--------getDbSourceBydbKey------------------从缓存中获取DB连接-------------------");
			return cacheDbSource;
		} else {
			DruidDataSource dataSource = getJdbcDataSource(dbSource, safeModel);
			if (dataSource != null && dataSource.isEnable()) {
				DataSourceCachePool.putCacheBasicDataSource(dbKey, dataSource);
			} else {
				throw new CcAdminException("动态数据源连接失败，dbKey：" + dbKey);
			}
			log.info("--------getDbSourceBydbKey------------------创建DB数据库连接-------------------");
			return dataSource;
		}
	}

	/**
	 * 关闭数据库连接池
	 *
	 * @param dbKey
	 * @return
	 */
	public static void closeDbKey(final String dbKey) {
		DruidDataSource dataSource = getDbSourceByDbKey(dbKey, false);
		try {
			if (dataSource != null && !dataSource.isClosed()) {
				dataSource.getConnection().commit();
				dataSource.getConnection().close();
				dataSource.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	public static JdbcTemplate getJdbcTemplate(String dbKey, Boolean safeModel) {
		DruidDataSource dataSource = getDbSourceByDbKey(dbKey, safeModel);
		return new JdbcTemplate(dataSource);
	}


	/**
	 * Executes the SQL statement in this <code>PreparedStatement</code> object,
	 * which must be an SQL Data Manipulation Language (DML) statement, such as <code>INSERT</code>, <code>UPDATE</code> or
	 * <code>DELETE</code>; or an SQL statement that returns nothing,
	 * such as setProjectPath DDL statement.
	 */
	/**
	 * @param dbKey
	 * @param sql
	 * @param safeModel 安全模式，如果为空，那么默认就是安全模式，除非显式指定为false，否则禁止执行除本机数据源以外的更新操作。
	 * @param param
	 * @return
	 */
	public static int update(final String dbKey, String sql, Boolean safeModel, Object... param) {
		int effectCount;
		JdbcTemplate jdbcTemplate = getJdbcTemplate(dbKey,safeModel);
		if (ArrayUtils.isEmpty(param)) {
			effectCount = jdbcTemplate.update(sql);
		} else {
			effectCount = jdbcTemplate.update(sql, param);
		}
		return effectCount;
	}

	/**
	 * 支持miniDao语法操作的Update
	 *
	 * @param dbKey 数据源标识
	 * @param sql   执行sql语句，sql支持minidao语法逻辑
	 * @param safeModel 安全模式，如果为空，那么默认就是安全模式，除非显式指定为false，否则禁止执行除本机数据源以外的更新操作。
	 * @param data  sql语法中需要判断的数据及sql拼接注入中需要的数据
	 * @return
	 */
	public static int updateByHash(final String dbKey, String sql, Boolean safeModel, HashMap<String, Object> data) {
		int effectCount;
		JdbcTemplate jdbcTemplate = getJdbcTemplate(dbKey,safeModel);
		//根据模板获取sql
		sql = FreemarkerParseFactory.parseTemplateContent(sql, data);
		NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate.getDataSource());
		effectCount = namedParameterJdbcTemplate.update(sql, data);
		return effectCount;
	}

	public static Object findOne(final String dbKey, String sql, Object... param) {
		List<Map<String, Object>> list;
		list = findList(dbKey, sql, param);
		if (oConvertUtils.listIsEmpty(list)) {
			log.error("Except one, but not find actually");
		}
		if (list.size() > 1) {
			log.error("Except one, but more than one actually");
		}
		return list.get(0);
	}

	/**
	 * 支持miniDao语法操作的查询 返回HashMap
	 *
	 * @param dbKey 数据源标识
	 * @param sql   执行sql语句，sql支持minidao语法逻辑
	 * @param data  sql语法中需要判断的数据及sql拼接注入中需要的数据
	 * @return
	 */
	public static Object findOneByHash(final String dbKey, String sql, HashMap<String, Object> data) {
		List<Map<String, Object>> list;
		list = findListByHash(dbKey, sql, data);
		if (oConvertUtils.listIsEmpty(list)) {
			log.error("Except one, but not find actually");
		}
		if (list.size() > 1) {
			log.error("Except one, but more than one actually");
		}
		return list.get(0);
	}

	/**
	 * 直接sql查询 根据clazz返回单个实例
	 *
	 * @param dbKey 数据源标识
	 * @param sql   执行sql语句
	 * @param clazz 返回实例的Class
	 * @param param
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <T> Object findOne(final String dbKey, String sql, Class<T> clazz, Object... param) {
		Map<String, Object> map = (Map<String, Object>) findOne(dbKey, sql, param);
		return ReflectHelper.setAll(clazz, map);
	}

	/**
	 * 支持miniDao语法操作的查询 返回单个实例
	 *
	 * @param dbKey 数据源标识
	 * @param sql   执行sql语句，sql支持minidao语法逻辑
	 * @param clazz 返回实例的Class
	 * @param data  sql语法中需要判断的数据及sql拼接注入中需要的数据
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <T> Object findOneByHash(final String dbKey, String sql, Class<T> clazz, HashMap<String, Object> data) {
		Map<String, Object> map = (Map<String, Object>) findOneByHash(dbKey, sql, data);
		return ReflectHelper.setAll(clazz, map);
	}

	public static List<Map<String, Object>> findList(final String dbKey, String sql, Object... param) {
		List<Map<String, Object>> list;
		JdbcTemplate jdbcTemplate = getJdbcTemplate(dbKey,false);
		if (ArrayUtils.isEmpty(param)) {
			list = jdbcTemplate.queryForList(sql);
		} else {
			list = jdbcTemplate.queryForList(sql, param);
		}
		return list;
	}

	/**
	 * 支持miniDao语法操作的查询
	 *
	 * @param dbKey 数据源标识
	 * @param sql   执行sql语句，sql支持minidao语法逻辑
	 * @param data  sql语法中需要判断的数据及sql拼接注入中需要的数据
	 * @return
	 */
	public static List<Map<String, Object>> findListByHash(final String dbKey, String sql, HashMap<String, Object> data) {
		List<Map<String, Object>> list;
		JdbcTemplate jdbcTemplate = getJdbcTemplate(dbKey,false);
		//根据模板获取sql
		sql = FreemarkerParseFactory.parseTemplateContent(sql, data);
		NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate.getDataSource());
		list = namedParameterJdbcTemplate.queryForList(sql, data);
		return list;
	}

	//此方法只能返回单列，不能返回实体类
	public static <T> List<T> findList(final String dbKey, String sql, Class<T> clazz, Object... param) {
		List<T> list;
		JdbcTemplate jdbcTemplate = getJdbcTemplate(dbKey,false);
		if (ArrayUtils.isEmpty(param)) {
			list = jdbcTemplate.queryForList(sql, clazz);
		} else {
			list = jdbcTemplate.queryForList(sql, clazz, param);
		}
		return list;
	}

	/**
	 * 支持miniDao语法操作的查询 返回单列数据list
	 *
	 * @param dbKey 数据源标识
	 * @param sql   执行sql语句，sql支持minidao语法逻辑
	 * @param clazz 类型Long、String等
	 * @param data  sql语法中需要判断的数据及sql拼接注入中需要的数据
	 * @return
	 */
	public static <T> List<T> findListByHash(final String dbKey, String sql, Class<T> clazz, HashMap<String, Object> data) {
		List<T> list;
		JdbcTemplate jdbcTemplate = getJdbcTemplate(dbKey, false);
		//根据模板获取sql
		sql = FreemarkerParseFactory.parseTemplateContent(sql, data);
		NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate.getDataSource());
		list = namedParameterJdbcTemplate.queryForList(sql, data, clazz);
		return list;
	}

	/**
	 * 直接sql查询 返回实体类列表
	 *
	 * @param dbKey 数据源标识
	 * @param sql   执行sql语句，sql支持 minidao 语法逻辑
	 * @param clazz 返回实体类列表的class
	 * @param param sql拼接注入中需要的数据
	 * @return
	 */
	public static <T> List<T> findListEntities(final String dbKey, String sql, Class<T> clazz, Object... param) {
		List<Map<String, Object>> queryList = findList(dbKey, sql, param);
		return ReflectHelper.transList2Entrys(queryList, clazz);
	}

	/**
	 * 支持miniDao语法操作的查询 返回实体类列表
	 *
	 * @param dbKey 数据源标识
	 * @param sql   执行sql语句，sql支持minidao语法逻辑
	 * @param clazz 返回实体类列表的class
	 * @param data  sql语法中需要判断的数据及sql拼接注入中需要的数据
	 * @return
	 */
	public static <T> List<T> findListEntitiesByHash(final String dbKey, String sql, Class<T> clazz, HashMap<String, Object> data) {
		List<Map<String, Object>> queryList = findListByHash(dbKey, sql, data);
		return ReflectHelper.transList2Entrys(queryList, clazz);
	}
}
